面向对象 - 对象的相等判断

面向对象 - 对象的相等判断

比较值: is 和 ==

is 比较的是两个实例对象内存地址是否一样,比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了,其实从严格意义上来说,**两个对象进行对比的规则,是对象的类的 __eq__ 方法定义的**,我们可以通过重写类的 __eq__ 来自定义通过 == 时的行为,这叫运算符重载。

官方文档中说 is 表示的是对象标示符(object identity),而 == 表示的是相等(equality)。is 的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中的地址是否一样,而 == 是用来检查两个对象是否相等。

我们在检查 a is b 的时候,其实相当于检查 id(a) == id(b)。而检查 a == b 的时候,实际是调用了对象 a 的 __eq()__ 方法,a == b 相当于 a.__eq__(b)。

一般情况下,如果 a is b 返回 True 的话,即 a 和 b 指向同一块内存地址的话,a == b 也返回 True,即 a 和 b 的值也相等。

内置类型均已重载 __eq__ 所以可以实现根据对象内容比较,自定义类需要自行重写 __eq__ 来实现通过 == 比较时根据内容判断是否相等,如果没有重 __eq__,那么在比较自定义类的两个实例的时候会一直返回 False

在 Java 中 == 用于比较对象内存,而 equals 方法比较对象内容,只不过 Java 无法实现运算符重载,不能自定 == 行为,不然也不需要 equals 方法了

运算符重载,确实是非常方便的东西。

对于数字、字符串、bool 等基本数据类型,他们都属于不可变对象,在 Pycharm 中,对这些对象进行 is== 的结果都是相同的,即这些对象都是单例对象,也就是同一个值在内存中只会有一个对象,甚至 None is None 也是 True。

在《函数.md》的 可更改(mutable)与不可更改(immutable)对象 小节中,我们讨论过这个概念

为什么,这是因为 Python 的性能优化机制:Python 的小整数池字符串驻留 (intern) 机制在起作用,这些机制在不同的场景下有不同的表现:

参考博客:【Python】浅谈 小整数池 + 字符串驻留 (Intern 机制)_何处闻韶的博客-CSDN博客

我实验的 Python 当前版本为 3.10.4,跟博客中的情况有所出入,看博客一定要自己实践,情况可能不一样

简单实践如下:

# is 和 ==
# ==比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了;而is比较的是两个实例对象内存地址是否一样。
# 我们可以通过重写类的 __eq__ 来自定义通过 == 时的行为,这叫运算符重载,内置类型均已重载 __eq__ 所以可以实现 根据对象内容比较,自定义类需要自行重写 __eq__ 来实现通过 == 比较时根据内容判断是否相等
# 在 Java 中  ==  用于比较对象内存,而equals方法比较对象内容,只不过Java无法实现运算符重载,不能自定 == 行为,不然也不需要equals方法了

print("-----------数字----------------")
# 数字
a = 1245678985
b = int(1245678985)
# True
print(a is b)
# True
print(a == b)

print("-----------小数----------------")
# 小数
a = 12456.78985
b = float(12456.78985)
# True
print(a is b)
# True
print(a == b)

print("-----------字符串----------------")
# 字符串
a = "1245678985"
b = str("1245678985")
# True
print(a is b)
# True
print(a == b)

print("-----------长字符串----------------")
# 长字符串
a = "hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world "
b = "hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world "
# True
print(a is b)
# True
print(a == b)

print("-----------bool----------------")
# bool
# True
print(True is True)
# True
print(True == True)

print("-----------列表----------------")
# 列表
a = ["12", 45, True]
b = ["12", 45, True]
# False
print(a is b)
# True
print(a == b)

print("-----------集合----------------")
# 集合
a = {12, 78, "aaa"}
b = {"aaa", 78, 12}
# False
print(a is b)
# True
print(a == b)

print("-----------None----------------")
# None
# False
print(None is not None)
# True
print(None is None)


print("-----------自定义类----------------")
class test_value_equal:
    def __init__(self, name: str, age: int):
        self.name: str = name
        self.age: int = age

    # 如果不重写 __eq__, a == b 会返回false
    def __eq__(self, other):
        return self.name == other.name and self.age == other.age


a = test_value_equal("xiashuo.xyz", 12)
b = test_value_equal("xiashuo.xyz", 12)
# False
print(a is b)
# 需要重写 __eq__ 方法,否则返回false。
# True
print(a == b)

print("-----------跨方法----------------")
# 跨调用栈
a = "xiashuo.xyz"

def test_value():
    b = "xiashuo.xyz"
    # True
    print(a is b)
    # True
    print(a == b)

test_value()

print("-----------跨类----------------")
# 跨类
class test_obj:
    def __init__(self):
        self.name = "xiashuo.xyz"

obj = test_obj()
# True
print(a is obj.name)
# True
print(a == obj.name)


print("-----------跨模块----------------")

from test_value import c

a = "xiashuo.xyz"
# False
print(a is c)
# True
print(a == c)

输出:

-----------数字----------------
True
True
-----------小数----------------
True
True
-----------字符串----------------
True
True
-----------长字符串----------------
True
True
-----------bool----------------
True
True
-----------列表----------------
False
True
-----------集合----------------
False
True
-----------None----------------
False
True
-----------自定义类----------------
False
True
-----------跨方法----------------
True
True
-----------跨类----------------
True
True
-----------跨模块----------------
False
True

一般来说,应该使用 == 比较两个对象是否相等,因为这是比较它们的内容是否相同;而 is 比较的是两个对象是否是同一个对象,通常用于判断一个变量是否为 None。

比较类型:tupe 和 isinstance 、 issubclass

type() 方法我们了解很多了,就是返回对象的类型,

isinstance() 返回对象是否是指定类或者指定类的子类的实例,在指定类型的地方可以输入一个元组,比如 isinstance(x, (A, B)),相当于 isinstance(x, A) or isinstance(x, B)

issubclass() 返回类型是否是指定类型的子类。自定类型是自身类型也会返回 true。在指定类型的地方可以输入一个元组,比如 issubclass(x, (A, B)),相当于 issubclass(x, A) or issubclass(x, B)

尽管 isinstance() 很灵活,但它没有执行 " 严格匹配 " 比较, 如果 obj 是一个给定类型的实例或者子类的实例,也会返回 True.
如果想进行严格匹配,仍需要 type(obj) is xxx 进行判断,不建议使用 type(obj) == xxx

简单实践如下:

# type isinstance

class base_example:
    pass


class type_checke_example(base_example):
    def __init__(self):
        self.name: str = "xiashuo.xyz"


obj = type_checke_example()
# <class '__main__.type_checke_example'>
print(type(obj))
# 检查两个对象是否是同一个类型
obj2 = type_checke_example()
# 用 is 比较合适 不要用 == ,因为类型对象也是唯一的
# True
print(type(obj) is type(obj2))
# True
print(type(obj) is type_checke_example)

# 类型对象也是唯一的
# True
print(type(123) is int)

# 检查一个实例是否是一个类的实例
is_class = isinstance(obj, type_checke_example)
# True
print(is_class)
# 这个实例是指定类型的子类的子类的实例,也会返回true
is_class = isinstance(obj, base_example)
# True
print(is_class)

# None 也是 对象
# True
print(isinstance(None, object))

# 父子类型检查
# False
print(issubclass(type_checke_example, int))
# True
print(issubclass(type_checke_example, base_example))
# True
print(issubclass(type_checke_example, type_checke_example))

输出

<class '__main__.type_checke_example'>
True
True
True
True
True
True
False
True
True